WCF的整体结构如下图所示
图上展示的是Client调用Service的过程. 在前面的笔记中已经提到, 调用过程也可以反向进行, 从Service回调Client, 这就是WCF中的双工通信.原先的Service和Client将发生对调,Service成为Client,Client成为Service。
WCF支持3种消息交换模式: 1. Request/Reply (请求/回复); 2. One-Way(单向); 3. Duplex(双工). 前面两种是基本的消息交换模式, 可以用OperationContract描述, Duplex可以看成是前两种基本消息交换模式的组合.
1.Request/Reply 默认方式
As the name implies, in these operations, the client issues a request in the form of a message and blocks until it gets the reply message. If the service does not respond within a default timeout of one minute, the client will get a TimeoutException. Request-reply is the default operation mode. Programming against request-reply operations is simple enough and resembles programming using the classic client/server model. The returned response message containing the results or returned values is converted to normal method return values. In addition, the proxy will throw an exception on the client side if there are any communication or service-side exceptions
2.One-Way
One-Way 这种方式在调用方法后会立即返回。需要注意的是 One-Way 不能用在非void,或者包含 out/ref 参数的方法上,会导致抛出 InvalidOperationException 异常。
There are cases when an operation has no return value, and the client does not care about the success or failure of the invocation. To support this sort of fire-and-forget invocation, WCF offers one-way operations: once the client issues the call, WCF generates a request message, but no correlated reply message will ever return to the client. As a result, one-way operations cannot return values, and any exceptions thrown
on the service side will not make their way to the client.
Ideally, when the client calls a one-way method, it should be blocked only for the briefest moment required to dispatch the call. However, in reality, one-way calls do not equate to asynchronous calls. When one-way calls reach the service, they may not be dispatched all at once but may instead be buffered on the service side to be dispatched one at a time, according to the service’s configured concurrency mode behavior. The
number of messages the service can buffer (be they one-way or request-reply operations) is a product of the configured channel and reliability mode. If the number of messages exceeds the buffer’s capacity, the client will be blocked even if it has issued a one-way call. However, once the call is deposited in the buffer, the client will be unblocked and can continue executing while the service processes the operation in
the background. 假如one-way call到达Service但没有立即被Dispatch, 而是在缓存队列中, 这是client会被阻塞
It’s also wrong to equate one-way calls with concurrent calls. If the client uses the same proxy yet utilizes multiple threads to invoke one-way calls, the calls may or may not execute concurrently on the service, and the exact nature of the interaction will be determined by the service concurrency management mode and the transport session (see Chapter 8 for more on this subject).All of the WCF bindings support one-way operations.
3.Duplex
WCF supports allowing a service to call back to its clients. During a callback, in many respects the tables are turned: the service is the client, and the client becomes the service (see Figure 5-1). Callback operations can be used in a variety of scenarios and applications, but they are especially useful when it comes to events, or notifying the client(s) that some event has happened on the service side.
Not all bindings support callback operations. Only bidirectional-capable bindings support callback operations. For example, because of its connectionless nature, HTTP cannot be used for callbacks, and therefore you cannot use callbacks over the BasicHttpBinding or the WSHttpBinding. The only two commonly used bindings that offer callbacks are the NetTcpBinding and the NetNamedPipeBinding, because by their very nature, the TCP and the IPC protocols support duplex communication.
一个服务契约若要定义回调,必须专门定义一个用于回调的契约。一个服务契约最多包含一个回调契约,一个服务契约一旦定义了回调契约那客户端必须支持这个回调。那如何为一个服务契约定义回调呢?使用ServiceContract特性的CallBackContract特性,代码如下:
1 | interface IMyContractCallback |
Client Callback Setup
It is up to the client to host the callback object and expose a callback endpoint. Recall from Chapter 1 that the innermost execution scope of the service instance is the instance context. The InstanceContext class provides a constructor that takes the service instance to the host:
All the client needs to do to host a callback object is instantiate the callback object and construct a context around it:1
2
3
4
5
6
7
8
9
10class MyCallback : IMyContractCallback
{
public void OnCallback()
{...}
}
IMyContractCallback callback = new MyCallback();
InstanceContext context = new InstanceContext(callback);
MyContractClient proxy = new MyContractClient(context);
proxy.DoSomething();
Service-Side Callback Invocation
The client-side callback endpoint reference is passed along with every call the client makes to the service, and it is part of the incoming message. The OperationContext class provides the service with easy access to the callback reference via the generic method GetCallbackChannel
1 | [] |
Callback reentrancy
The service may also want to invoke the callback reference that’s passed in (or a saved copy of it) during the execution of a contract operation. However, such invocations are disallowed by default. The reason is the default service concurrency management.By default, the service class is configured for single-threaded access: the service instance context is associated with a lock, and only one thread at a time can own the
lock and access the service instance inside that context. Calling out to the client during an operation call requires blocking the service thread and invoking the callback. The problem is that processing the reply message from the client on the same channel once the callback returns requires reentering the same context and negotiating ownership of the same lock, which will result in a deadlock. Note that the service may still invoke callbacks to other clients or call other services; it is the callback to its calling client that will cause the deadlock
To prevent such a deadlock, if the single-threaded service instance tries to call back to its client, WCF will throw an InvalidOperationException. There are three possible solutions. The first is to configure the service for multithreaded access. Callbacks to the calling client will then be allowed because the service instance will not be associated with a lock; however, this will increase the burden on the service developer, because of the need to provide synchronization for the service. The second solution is to configure the service for reentrancy. When configured for reentrancy, the service instance context is still associated with a lock, and only single-threaded access is allowed. However, if the service is calling back to its client, WCF will silently release the lock first. you can set the concurrency behavior to either multithreaded or reentrant using the Concurrency Mode property of the ServiceBehavior attribute:1
2
3
4
5
6public enum ConcurrencyMode
{
Single, //Default
Reentrant,
Multiple
}
1 | [] |
The third solution that allows the service to safely call back to the calling client is to configure the callback contract operations as one-way operations. Doing so will enable the service to call back even when the concurrency mode is set to singlethreaded, because there will not be any reply message to contend for the lock.
1 | //Example 5-8. One-way callbacks are allowed by default |
Callbacks and the UI Synchronization Context
Configuring the callback for affinity to the UI thread may trigger a deadlock. Suppose a Windows Forms client establishes an affinity between a callback object (or even itself) and the UI synchronization context, and then calls a service, passing the callback reference. The service is configured for reentrancy, and it calls back to the client.
A deadlock now occurs because the callback to the client needs to execute on the UI thread, and that thread is blocked waiting for the service call to return. For example,Example 8-22 has the potential for this deadlock. Configuring the callback as a oneway operation will not resolve the problem here, because the one-way call still needs to be marshaled first to the UI thread. The only way to resolve the deadlock in this case is to turn off using the UI synchronization context by the callback, and to manually and asynchronously marshal the update to the form using its synchronization context. Example 8-24 demonstrates using this technique.
Thread Affinity (线程亲和性) 和 marshal(封送), 这两个概念是一块儿的
Whenever an affinity to a particular thread or threads is expected, the service cannot simply execute the call on the incoming WCF worker thread. Instead, the service must marshal the call to the correct thread(s) required by the resource that it accesses.
marshal指的是A线程中向B线程发送一个调用请求, 例如工作线程marshal(封送)到UI线程
UseSynchronizationContext的运用
If the thread that is opening the host has a synchronization context and UseSynchronizationContext is true, WCF will establish an affinity between that synchronization context and all of the instances of the service that is hosted by that host. WCF will automatically marshal all of the incoming calls to the service’s synchronization context.
The default value of UseSynchronizationContext is true,
The classic use of UseSynchronizationContext is to enable the service to update UI controls and windows directly. WCF greatly simplifies UI updates by providing an affinity between all of the service instances from a particular host and a specific UI thread.
Whenever you use hosting on the UI thread, deadlocks are possible. For example, the following setup is guaranteed to result with a deadlock: A Windows Forms application is hosting a service with UseSynchronizationContext set to true, and UI thread affinity is established. The Windows Forms application then calls the service over one of its endpoints. The call to the service blocks the UI thread, while WCF posts a message to the UI thread to invoke the service. That message is never processed, because of the blocking UI thread—hence, the deadlock.
UseSynchronizationContext 默认为true, 那么UI线程调用服务, 服务回调默认也发生在UI线程中.
假如调用不是request-reply, 那么会出现死锁: UI线程等待服务结束调用(堵塞), 服务结束调用, 需要回调返回, 而回调又需要UI线程, 所以死循环了
一个解决方法是UseSynchronizationContext 设为false, 那么回调就会发生在work thread中, 然后再从work thread封送到UI线程, 假如需要操作UI控件的话
1 | //Example 8-22 |
1 | //Example 8-24. Avoiding a callback deadlock on the UI thread |
WCF解决死锁的策略
WCF resolves service call deadlocks by eventually timing out the call and throwing a TimeoutException
参考:
Writing Smart Clients by Using Windows Communication Foundation(https://msdn.microsoft.com/en-us/library/cc294424.aspx)